58  绘制饼图

58.1 引言比例的可视化

饼图(Pie Chart)是展示部分与整体关系的经典工具。在金融分析中,饼图帮助我们: - 资产配置:展示投资组合的构成 - 收入结构:主营业务与其他业务的占比 - 市场份额:不同公司的市场占有率 - 行业分布:投资的行业配置

58.2 饼图的数学基础

饼图将圆划分为若干扇形,每个扇形的角度对应一个类别的比例:

\[ \theta_i = \frac{x_i}{\sum_{j=1}^n x_j} \times 360° \]

其中: - \(x_i\):第 \(i\) 个类别的数值 - \(\theta_i\):第 \(i\) 个扇形的角度

58.3 基础饼图

平台任务1解答代码

以下代码与教学平台任务要求完全一致:

列表 58.1
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
import pandas as pd  # 导入Pandas数据分析库

import numpy as np  # 导入NumPy数值计算库

time=['2018年12月末','2019年3月末','2019年6月末','2019年9月末']#创建日期的列表

name=['人民币贷款','债券','委托贷款','信托贷款','股票','承兑汇票','贷款核销','外币贷款']#创建指标名称的列表

# 创建NumPy数组datas
datas=np.array([[134.69,140.98,144.71,148.58],[29.25,30.53,31.89,33.54],[12.36,12.15,11.89,11.73],[7.85,7.88,7.88,7.68],[7.01,7.06,7.13,7.24],[3.81,4.01,3.77,3.28],[3.01,3.18,3.43,3.66],

[2.21,2.18,2.21,2.19]])#创建具体数据的数组

AFRE = pd.DataFrame(data=datas,index=name,columns=time)  # 创建数据框AFRE

print(AFRE)  # 输出变量AFRE的值

平台任务3解答代码

以下代码与教学平台任务要求完全一致:

列表 58.2
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
import matplotlib.pyplot as plt  # 导入Matplotlib绑图库
import pandas as pd  # 导入Pandas数据分析库
import numpy as np  # 导入NumPy数值计算库
plt.rcParams["font.sans-serif"] = ["SimHei"]  # 设置Matplotlib全局参数
time=['2018年12月末','2019年3月末','2019年6月末','2019年9月末']#创建日期的列表
name=['人民币贷款','债券','委托贷款','信托贷款','股票','承兑汇票','贷款核销','外币贷款']#创建指标名称的列表
# 创建NumPy数组datas
datas=np.array([[134.69,140.98,144.71,148.58],[29.25,30.53,31.89,33.54],[12.36,12.15,11.89,11.73],[7.85,7.88,7.88,7.68],[7.01,7.06,7.13,7.24],[3.81,4.01,3.77,3.28],[3.01,
3.18,3.43,3.66],[2.21,2.18,2.21,2.19]])#创建具体数据的数组
AFRE = pd.DataFrame(data=datas,index=name,columns=time)  # 创建数据框AFRE
plt.figure(figsize=(11,11))  # 创建图形画布
plt.subplot(2,2,1)#第1行、第1列子图
plt.pie(x=AFRE.iloc[:,0],labels=AFRE.index,labeldistance=1.03,counterclock=False,textprops={'fontsize':12})#绘制2018年12月末各项融资存量的饼图
plt.axis('equal')#使饼图是一个圆形
plt.title(u"2018年12月末",fontsize=14)  # 设置图表标题
plt.subplot(2,2,2)#第1行、第2列子图
plt.pie(x=AFRE.iloc[:,1],labels=AFRE.index,labeldistance=1.03,counterclock=False,textprops={'fontsize':12})#绘制2019年3月末各项融资存量的饼图
plt.axis('equal')#使饼图是一个圆形
plt.title(u"2019年3月末",fontsize=14)  # 设置图表标题
plt.subplot(2,2,3)#第2行、第1列子图
plt.pie(x=AFRE.iloc[:,2],labels=AFRE.index,labeldistance=1.03,counterclock=False,textprops={'fontsize':12})#绘制2019年6月末各项融资存量的饼图
plt.axis('equal')#使饼图是一个圆形
plt.title(u"2019年6月末",fontsize=14)  # 设置图表标题
plt.subplot(2,2,4)#第2行、第2列子图
plt.pie(x=AFRE.iloc[:,3],labels=AFRE.index,labeldistance=1.03,counterclock=False,textprops={'fontsize':12})#绘制2019年9月末各项融资存量的饼图
plt.axis('equal')#使饼图是一个圆形
plt.title(u"2019年9月末",fontsize=14)  # 设置图表标题
plt.show()  # 显示图形
plt.savefig("2.png")  # 保存图形至文件
列表 58.3
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
import matplotlib.pyplot as plt  # 导入Matplotlib绑图库
import pandas as pd  # 导入Pandas数据分析库
import numpy as np  # 导入NumPy数值计算库
plt.rcParams["font.sans-serif"] = ["SimHei"]  # 设置Matplotlib全局参数
time=['2018年12月末','2019年3月末','2019年6月末','2019年9月末']#创建日期的列表
name=['人民币贷款','债券','委托贷款','信托贷款','股票','承兑汇票','贷款核销','外币贷款']#创建指标名称的列表
# 创建NumPy数组datas
datas=np.array([[134.69,140.98,144.71,148.58],[29.25,30.53,31.89,33.54],[12.36,12.15,11.89,11.73],[7.85,7.88,7.88,7.68],[7.01,7.06,7.13,7.24],[3.81,4.01,3.77,3.28],[3.01,
3.18,3.43,3.66],[2.21,2.18,2.21,2.19]])#创建具体数据的数组
AFRE = pd.DataFrame(data=datas,index=name,columns=time)  # 创建数据框AFRE
plt.figure(figsize=(9,6))  # 创建图形画布
plt.pie(x=AFRE.iloc[:,-1],labels=AFRE.index,textprops={"fontsize":13}) #绘制2018年末各项融资存量的饼图
plt.axis("equal") #使饼图是一个图形
plt.legend(fontsize=12)  # 添加图例
plt.title(u"2019年9月末各类融资存量的占比",fontsize=13)  # 设置图表标题
plt.show()  # 显示图形
plt.savefig("1.png")  # 保存图形至文件

关键参数解析:

  1. autopct:显示百分比的格式字符串
    • '%1.1f%%':保留1位小数
    • '%1.2f%%':保留2位小数
    • '%d%%':显示整数
  2. startangle:起始角度
    • 90(或-90):从12点方向开始,符合时钟习惯
    • 0:从3点方向开始
    • 通常用90度更直观
  3. explode:突出显示
    • 元素为0:正常显示
    • 元素>0:向外偏移,突出该扇形

58.4 环形图

列表 58.4
# =============================================================================
# 题目: 环形图——收入构成分析
# =============================================================================
# 本代码展示如何绘制环形图(甜甜圈图),在饼图中心添加信息
# 应用于金融场景:展示收入构成,中心显示总收入

# ==================== 收入数据 ====================
revenue_sources = ['主营业务', '其他业务', '投资收益', '资产处置']  # 收入来源
revenues = [850, 120, 80, 50]  # 各项收入,单位:亿元
colors_revenue = ['#2E86AB', '#008080', '#F0A700', '#E3120B']
# 配色:蓝、青、黄、红

# ==================== 绘制环形图 ====================
fig, ax = plt.subplots(figsize=(8, 8))  # 创建画布和坐标轴对象

# 绘制饼图,然后中心挖空形成环形
wedges, texts, autotexts = ax.pie(revenues, labels=revenue_sources,
                                   colors=colors_revenue,
                                   autopct='%1.1f%%',
                                   startangle=90,
                                   pctdistance=0.85)
# pctdistance=0.85: 百分比标签距离圆心的位置(0-1之间)
# 0.85表示距离圆心85%的位置

# ==================== 创建白色圆心,形成环形 ====================
center_circle = plt.Circle((0, 0), 0.60, fc='white')
# 创建一个白色圆,半径0.60(相对于饼图半径1),覆盖在中心
# (0, 0)是圆心坐标
ax.add_artist(center_circle)  # 将圆心添加到图表中

# ==================== 在中心添加总计 ====================
total_revenue = sum(revenues)  # 计算总收入
ax.text(0, 0, f'总收入\n{total_revenue}亿元',
        ha='center', va='center',  # 水平和垂直居中
        fontsize=14, fontweight='bold')  # 字体大小14,加粗
# 使用\n换行,将"总收入"和数值分两行显示

# ==================== 添加标题 ====================
plt.title('收入构成', fontsize=16, fontweight='bold', pad=20)
plt.tight_layout()
plt.show()

# ==================== 收入结构分析 ====================
print('收入结构分析:')  # 打印标题
for i, source in enumerate(revenue_sources):  # 遍历所有收入来源
    pct = revenues[i] / total_revenue * 100  # 计算百分比
    print(f'{source}: {revenues[i]}亿元 ({pct:.1f}%)')  # 打印每项收入和占比

环形图的优势: - 更现代、美观 - 中心可放置额外信息(总计、图标等) - 减小扇形面积的视觉误导

58.5 条形图 vs 饼图

列表 58.5
# =============================================================================
# 题目: 对比——条形图与饼图
# =============================================================================
# 本代码展示同一数据用饼图和条形图两种方式呈现,对比优缺点
# 应用于金融场景:帮助读者选择合适的图表类型

# ==================== 同一数据,两种展示方式 ====================
companies = ['公司A', '公司B', '公司C', '公司D', '公司E']  # 公司名称
market_share = [35, 25, 20, 12, 8]  # 市场份额,单位:%
colors_comparison = ['#E3120B', '#008080', '#F0A700', '#2C3E50', '#8E9EAA']
# 配色方案,保持一致性

# ==================== 创建子图 ====================
fig, axes = plt.subplots(1, 2, figsize=(14, 6))  # 1行2列的子图布局

# ==================== 左:饼图 ====================
axes[0].pie(market_share, labels=companies, colors=colors_comparison,
            autopct='%1.1f%%', startangle=90)  # 绘制饼图
axes[0].set_title('饼图', fontsize=14, fontweight='bold')  # 子图标题

# ==================== 右:条形图 ====================
bars = axes[1].bar(companies, market_share, color=colors_comparison, alpha=0.8)
# 绘制垂直条形图,alpha=0.8设置透明度
axes[1].set_title('条形图', fontsize=14, fontweight='bold')
axes[1].set_ylabel('市场份额(%)', fontsize=12)  # Y轴标签
axes[1].grid(axis='y', alpha=0.3)  # 只显示Y轴网格线

# ==================== 添加数值标签 ====================
for bar in bars:  # 遍历每个条形
    height = bar.get_height()  # 获取条形高度
    axes[1].text(bar.get_x() + bar.get_width()/2., height,  # 计算标签位置
                f'{height}%', ha='center', va='bottom', fontsize=10)  # 添加标签
# bar.get_x(): 条形左边界坐标
# bar.get_width(): 条形宽度
# ha='center': 水平居中对齐
# va='bottom': 垂直底部对齐(标签在条形上方)

plt.tight_layout()  # 调整布局
plt.show()  # 显示

print('使用建议:')  # 打印使用建议
print('- 饼图:展示构成,适合类别较少(≤5)的情况')
print('- 条形图:精确比较数值,适合类别较多或需要精确比较')

选择指南:

场景 推荐图表 理由
类别≤5,重点是构成 饼图 直观展示比例
类别>5 条形图 避免饼图过于细碎
需要精确比较数值 条形图 长度比角度更易比较
层次结构(饼中饼) 环形图或嵌套饼图 展示多级构成
时间序列变化 堆叠条形图 饼图难以展示变化

58.6 嵌套饼图

列表 58.6
# =============================================================================
# 题目: 嵌套饼图——多层级数据展示
# =============================================================================
# 本代码展示如何绘制嵌套饼图,展示两层级的层次结构
# 应用于金融场景:展示大类资产及其细分

# ==================== 两层数据 ====================
# 外层:大类资产
major_assets = ['权益类', '固定收益类', '另类投资']  # 三大类
major_alloc = [50, 35, 15]  # 大类配置比例

# 内层:细分类别
sub_assets = ['A股', '港股', '国债', '信用债', '商品', 'REITs']  # 细分
sub_alloc = [30, 20, 20, 15, 8, 7]  # 细分配置比例

# 确定每个子类别属于哪个大类
major_index = [0, 0, 1, 1, 2, 2]  # 对应major_assets的索引
# A股、港股属于权益类(索引0)
# 国债、信用债属于固定收益类(索引1)
# 商品、REITs属于另类投资(索引2)

# ==================== 绘制嵌套饼图 ====================
fig, ax = plt.subplots(figsize=(10, 10))  # 创建10×10英寸画布

# ==================== 内层饼图 ====================
wedges_inner, texts_inner, autotexts_inner = ax.pie(
    sub_alloc,
    radius=0.7,  # 内层半径0.7
    labels=sub_assets,  # 标签
    colors=['#E3120B', '#F0656E', '#008080', '#46A1A8', '#F0A700', '#F7C555'],
    # 使用渐变色,同一大类用相近颜色
    autopct='%1.1f%%',
    startangle=90,
    pctdistance=0.85,  # 百分比标签距离
    labeldistance=0.6   # 类别标签距离
)

# ==================== 外层饼图 ====================
wedges_outer, texts_outer, autotexts_outer = ax.pie(
    major_alloc,
    radius=1.0,  # 外层半径1.0
    labels=major_assets,
    colors=['#E3120B', '#008080', '#F0A700'],  # 大类颜色
    autopct='%1.1f%%',  # 显示百分比(需返回3个值)
    startangle=90,
    labeldistance=1.05,  # 标签距离略大于1,避免与内层重叠
    textprops={'fontsize': 12, 'fontweight': 'bold'}  # 标签字体加粗
)

# ==================== 添加标题 ====================
plt.title('投资组合多层级构成', fontsize=16, fontweight='bold', pad=20)
plt.tight_layout()
plt.show()

# ==================== 打印层级结构 ====================
print('投资组合层级结构:')  # 打印标题
for i, major in enumerate(major_assets):  # 遍历每个大类
    sub_indices = [j for j, x in enumerate(major_index) if x == i]
    # 找出属于该大类的所有子类别的索引
    print(f'\n{major} ({major_alloc[i]}%):')  # 打印大类名称和比例
    for idx in sub_indices:  # 遍历该大类的子类别
        print(f'  - {sub_assets[idx]}: {sub_alloc[idx]}%')  # 打印子类别
        # 缩进两个空格,表示层级关系

58.7 金融应用行业配置

列表 58.7
# =============================================================================
# 题目: 基金行业配置分析
# =============================================================================
# 本代码展示如何对比基金与基准的行业配置差异
# 应用于金融场景:基金投资组合的行业配置偏离度分析

# ==================== 行业配置数据 ====================
industries = ['金融', '科技', '消费', '医疗', '新能源', '原材料', '其他']  # 行业列表
fund_allocation = [25, 20, 18, 12, 10, 8, 7]  # 基金配置比例
benchmark_allocation = [22, 15, 20, 10, 12, 10, 11]  # 基准(如沪深300)配置比例

# ==================== 创建子图 ====================
fig, axes = plt.subplots(1, 2, figsize=(14, 7))  # 1行2列

# ==================== 左:基金配置(饼图) ====================
axes[0].pie(fund_allocation, labels=industries,
            autopct='%1.1f%%', startangle=90,
            colors=['#2E86AB', '#008080', '#E3120B', '#F0A700',
                    '#2C3E50', '#8E9EAA', '#A8B5BF'])
axes[0].set_title('基金行业配置', fontsize=14, fontweight='bold')

# ==================== 右:与基准对比(条形图) ====================
x = np.arange(len(industries))  # 生成0-6的整数序列,作为X轴位置
width = 0.35  # 条形宽度0.35

# 绘制两组条形图,并排显示
axes[1].bar(x - width/2, fund_allocation, width,
           label='基金', color='#2E86AB', alpha=0.8)
# x - width/2: 左移半个条形宽度,居中显示
axes[1].bar(x + width/2, benchmark_allocation, width,
           label='基准', color='#E3120B', alpha=0.8)
# x + width/2: 右移半个条形宽度

# ==================== 添加坐标轴标签和标题 ====================
axes[1].set_xlabel('行业', fontsize=12)
axes[1].set_ylabel('配置比例(%)', fontsize=12)
axes[1].set_title('vs 基准对比', fontsize=14, fontweight='bold')
axes[1].set_xticks(x)  # 设置X轴刻度位置
axes[1].set_xticklabels(industries, rotation=45)  # 设置刻度标签,旋转45度避免重叠
axes[1].legend(fontsize=11)  # 显示图例
axes[1].grid(axis='y', alpha=0.3)  # Y轴网格线

plt.tight_layout()
plt.show()

# ==================== 计算偏离度 ====================
deviation = [f - b for f, b in zip(fund_allocation, benchmark_allocation)]
# 计算基金与基准的差值
df_deviation = pd.DataFrame({
    '行业': industries,
    '基金': fund_allocation,
    '基准': benchmark_allocation,
    '偏离度': deviation  # 正值表示超配,负值表示低配
})
print('\n配置偏离分析:')  # 打印标题
print(df_deviation)  # 打印偏离度表格

# ==================== 分析超配和低配 ====================
print(f'\n超配: {[industries[i] for i, d in enumerate(deviation) if d > 0]}')
# 列出所有超配行业(偏离度>0)
print(f'低配: {[industries[i] for i, d in enumerate(deviation) if d < 0]}')
# 列出所有低配行业(偏离度<0)

58.8 饼图的注意事项

何时使用饼图: ✅ 适合: - 展示部分与整体的关系 - 类别较少(≤6个) - 各部分比例差异明显 - 重点在于构成,非精确比较

不适合: - 类别过多(>8个) - 需要精确比较数值 - 各部分比例相近(难以区分) - 需要展示时间序列变化

最佳实践:

  1. 排序:按大小顺时针排列,从12点开始
  2. 突出关键:用explode或颜色突出重点
  3. 颜色:使用对比色,避免彩虹色
  4. 标签:直接标注,避免图例来回对照
  5. 总数:在标题或中心说明总数值